#include <gflags/gflags.h>
#include <iostream>

#include "aggregator/Aggregator.h"
#include "atomizer/Atomizer.h"
#include "common/BlockingQueue.h"
#include "common/EventBase.h"
#include "common/Network.h"
#include "common/Pipeline.h"
#include "common/TopologyUpdate.h"
#include "dataplane/ModelManager.h"
#include "model-updater/ModelUpdater.h"
#include "normalizer/Normalizer.h"
#include "server/Server.h"

#include <folly/ProducerConsumerQueue.h>

#include <map>

DEFINE_uint32(port, 6666, "The server port");

template <typename T>
T*
startThread(
    std::vector<std::thread>& allThreads,
    std::vector<std::unique_ptr<nexus::EventBase>>& orderedEvbs,
    const std::string& name,
    std::unique_ptr<T> instance) {
  auto t = instance.get();
  auto evb = std::unique_ptr<nexus::EventBase>(
      reinterpret_cast<nexus::EventBase*>(instance.release()));
  allThreads.emplace_back(std::thread([evb = evb.get(), name]() noexcept {
    XLOG(INFO) << "Starting " << name;
    evb->run();
    XLOG(INFO) << "Stopped " << name;
  }));
  evb->waitUntilRunning();
  orderedEvbs.emplace_back(std::move(evb));
  return t;
}

template <typename T>
T*
startThread(
    std::vector<std::thread>& allThreads,
    const std::string& name,
    std::unique_ptr<T> instance) {
  auto t = instance.get();
  auto inst = instance.release();
  allThreads.emplace_back(std::thread([name, inst]() noexcept {
    XLOG(INFO) << "Starting " << name << " thread";
    inst->run();
    XLOG(INFO) << "Stopped " << name << " thread";
  }));

  return t;
}

int
main(int argc, char** argv) {
  std::cout << "Greetings!" << std::endl;

  gflags::SetUsageMessage("Usage: ./nexus -port <gRPC server port>");
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  std::vector<std::thread> allThreads;
  std::vector<std::unique_ptr<nexus::EventBase>> orderedEvbs;

  nexus::BlockingQueue<nexus::TopologyUpdate> topologyUpdatesQueue{100};
  nexus::BlockingQueue<std::string> routeUpdatesQueue{100};

  nexus::Pipeline pipeline(100, 100, 100, 100);

  nexus::Network network("Net");
  std::map<std::string, nexus::Network> networks;

  auto n = std::make_unique<nexus::Normalizer>(pipeline);

  auto nomarlizer = startThread(allThreads, "nomarlizer", std::make_unique<nexus::Normalizer>(pipeline));

  auto atomizer = startThread(allThreads, "atomizer", std::make_unique<nexus::Atomizer>(pipeline, network));

  auto aggregator = startThread(allThreads, "aggregator", std::make_unique<nexus::Aggregator>(pipeline));

  auto modelUpdater = startThread(allThreads, "modelUpdater", std::make_unique<nexus::ModelUpdater>(pipeline, network));

  // auto modelManager = startThread(
  //     allThreads,
  //     orderedEvbs,
  //     "modelManager",
  //     std::make_unique<nexus::ModelManager>(
  //         routeUpdatesQueue, topologyUpdatesQueue));

  // auto server = startThread(
  //     allThreads, "grpcServer", std::make_unique<nexus::Server>(FLAGS_port));

  auto server = std::make_unique<nexus::Server>(
      FLAGS_port, pipeline, topologyUpdatesQueue, networks);
  // std::thread serverThread([&allThreads, &server] {
  //   server->run();
  // });
  allThreads.emplace_back(std::thread([&server] { server->run(); }));

  for (auto& t : allThreads) {
    t.join();
  }

  // auto server = std::make_unique<nexus::Server>(FLAGS_port,
  // routeUpdatesQueue); server->run();

  return 0;
}